package com.example.feigner;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * @author cxx
 * @create 2020-01-16 21:56
 **/
public class IFeignClientsRegistrar implements ImportBeanDefinitionRegistrar ,EnvironmentAware,BeanFactoryAware {

    private Environment environment;

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassLoader classLoader = metadata.getClass().getClassLoader();
        Map<String, Object>  map = metadata.getAnnotationAttributes ("com.example.feigner.EnableIFeignClients");
        Class[] clients = (Class[]) map.get ("clients");
        for (Class clz : clients) {
            IFeignClient feignClient = AnnotationUtils.findAnnotation (clz, IFeignClient.class);
            String value = environment.resolvePlaceholders (feignClient.value ());
            Object instance = Proxy.newProxyInstance (classLoader, new Class[]{clz}, new InvocationHandler () {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    RequestMapping annotation = AnnotationUtils.findAnnotation (method, RequestMapping.class);
                    if(annotation != null){
                        String url = "http://"+value+annotation.value ()[0];
                        RestTemplate iRestTemplate = beanFactory.getBean ("iRestTemplate", RestTemplate.class);
                        return iRestTemplate.getForObject (url, String.class);
                    }
                    return null;
                }
            });
            BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition (IFeignFactoryBean.class).getBeanDefinition ();
            ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues ();
            ConstructorArgumentValues argumentValues = new ConstructorArgumentValues ();
            argumentValues.addIndexedArgumentValue (0,clz);
            argumentValues.addIndexedArgumentValue (1,instance);
            constructorArgumentValues.addArgumentValues (argumentValues);
            registry.registerBeanDefinition ("IFeign."+clz.getSimpleName (),beanDefinition);
            return;
        }
    }

    /**
     * Set the {@code Environment} that this component runs in.
     *
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.environment=environment;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory=beanFactory;
    }
}

class IFeignFactoryBean implements FactoryBean<Object> {

    private Class clz;

    private Object instance;

    public IFeignFactoryBean(Class clz, Object instance) {
        this.clz = clz;
        this.instance = instance;
    }

    @Override
    public Object getObject() throws Exception {
        return instance;
    }

    @Override
    public Class<?> getObjectType() {
        return clz;
    }
}
